package com.ssm.basis.web.config.security;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * 安全配置
 * https://blog.csdn.net/qq_35067322/article/details/116178152
 */
// https://www.jianshu.com/p/fe1194ca8ecd
// @Order(1)
@Configuration(proxyBeanMethods = false)
// securedEnabled：@Secured（必须要有 ROLE_ 前缀）拥有某些角色才能访问
// prePostEnabled：@PreAuthorize 方法访问前后进行验证
// jsr250Enabled：@RolesAllowed
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 不需要认证的 url，不需要放开 "/login"
     */
    static final String[] PERMIT_URL = new String[]{
            "/actuator/**", "/error"
    };

    /**
     * 静态资源
     */
    static final String[] IGNORING_URL = new String[]{
            "/", "/favicon.ico", "/webjars/**", "/unauth.html", "/router.html", "/h2/**", "/druid/**",
            "/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs", "/v3/api-docs/**", "/swagger-ui.html"
    };

    /**
     * @see DefaultSecurityFilterChain 安全拦截机制
     * @see org.springframework.security.config.annotation.web.builders.FilterOrderRegistration
     * https://segmentfault.com/a/1190000020769672
     * https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-security-filters
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // CorsFilter，允许跨域请求，默认情况下使用名为 corsConfigurationSource 的 Bean
        http.cors(withDefaults());

        // CsrfFilter，跨站点请求伪造
        // https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#servlet-csrf-include-ajax
        http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).disable();

        // 退出：LogoutFilter，在开启 CSRF 后仍然使用 GET 方式提交退出请求，https://blog.csdn.net/mrleeyongsheng/article/details/78886184
        // 退出成功后 Handler 处理和 url 跳转只能二选一
        http.logout()
                // .logoutUrl("/logout")
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", HttpMethod.GET.name()))
                // .logoutSuccessUrl("/logout/success").permitAll()
                .logoutSuccessHandler((request, response, authentication) -> {
                    request.setAttribute("authentication", authentication);
                    request.getRequestDispatcher("/logout/success").forward(request, response);
                }).deleteCookies("SESSION");

        // 登录：UsernamePasswordAuthenticationFilter，开启 CSRF 后需要附带发送 CSRF token
        http.formLogin()
                // .loginPage("/loginPage")
                // .loginProcessingUrl("/loginProcess")
                // .successForwardUrl("/login/success")
                // .failureForwardUrl("/login/failure")
                .successHandler((request, response, authentication) -> {
                    request.setAttribute("auth", authentication);
                    request.getRequestDispatcher("/login/success").forward(request, response);
                }).failureHandler((request, response, exception) -> {
                    request.setAttribute("ex", exception);
                    request.getRequestDispatcher("/login/failure").forward(request, response);
                });

        // DigestAuthenticationFilter
        // 验证码登录：https://blog.csdn.net/qq_35067322/article/details/107526859
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);
        // http.addFilterBefore(xxxxFilter, UsernamePasswordAuthenticationFilter.class);

        // Basic 认证：BasicAuthenticationFilter
        http.httpBasic();

        // SessionManagementFilter、SessionRepositoryFilter
        // https://blog.csdn.net/andy_zhang2007/article/details/93398333
        http.sessionManagement(httpSecuritySessionManagementConfigurer -> {
            httpSecuritySessionManagementConfigurer.maximumSessions(1);
        });

        // 异常：ExceptionTranslationFilter，401、403 等
        http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
            request.setAttribute("ex", accessDeniedException);
            request.getRequestDispatcher("/access/denied").forward(request, response);
        }).authenticationEntryPoint((request, response, authException) -> {
            request.setAttribute("ex", authException);
            request.getRequestDispatcher("/access/authentication").forward(request, response);
        });

        // url 拦截
        http.antMatcher("/**")
                // .authorizeRequests()
                .authorizeHttpRequests()
                // 不需要通过登录验证就可以被访问的资源路径
                .antMatchers(PERMIT_URL).permitAll()
                // .antMatchers("/xxx").hasAuthority("admin")
                // .antMatchers("/xxx").hasAnyAuthority("admin", "xxx")
                // .antMatchers("/xxx").hasRole("ROLE_sale")
                // .antMatchers("/xxx").access("hasRole('ROLE_sale')")
                // .antMatchers("/xxx").access("@class.method(request,authentication)") // 自定义访问控制逻辑
                // .antMatchers("/xxx").hasAnyRole("ROLE_sale", "ROLE_xxx")
                // .regexMatchers(HttpMethod.GET, ".+[.]png").permitAll()
                // server.servlet.context-path: ssm
                // .antMatchers("/ssm/hw").hasIpAddress("127.0.0.1")
                // https://docs.spring.io/spring-security/site/docs/5.4.5/reference/html5/#el-common-built-in
                // .mvcMatchers("/hw").servletPath("/ssm").access("permitAll")
                // 其它路径都需要登陆认证
                .anyRequest()
                // .access((authenticationSupplier, requestAuthorizationContext) -> { // 动态权限
                //     // 当前用户的权限信息，比如角色
                //     Collection<? extends GrantedAuthority> authorities = authenticationSupplier.get().getAuthorities();
                //     // 当前请求上下文，可以获取携带的参数
                //     Map<String, String> variables = requestAuthorizationContext.getVariables();
                //     // 可以获取原始 request 对象
                //     HttpServletRequest request = requestAuthorizationContext.getRequest();
                //     // todo 根据这些信息和业务写逻辑即可，最终决定是否授权 isGranted
                //     boolean isGranted = true;
                //     return new AuthorizationDecision(isGranted);
                // })
                .authenticated();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // permitAll 还是要走过滤器，直到最后 FilterSecurityInterceptor 认定是可以放过的，才能访问，这里配置是不走过滤器链
        web.ignoring().antMatchers(IGNORING_URL);
    }
}
